#include "Directorio.h"
#include "../../recursoDeAlmacenamiento/RecursoDeAlmacenamientoDirecto.h"
#include "Bucket.h"
#include "../../claves/Key.h"
#include "../Elemento.h"
#include "../../recursoDeAlmacenamiento/FabricaRecursosAlmacenamiento.h"
#include "../../claves/TipoClave.h"
#include "../../componentes/RegistroVariable.h"
#include "../../estrategiaAlmacenamiento/IteradorRegistrosFijos.h"

Directorio::Directorio(cantBytes tamanioBucket, string path,TipoClave* extractor) {

	this->tamanioBucket = tamanioBucket;
	this->bucketActual = NULL;
	this->extractor = extractor;

	crearOLevantar(path);

}

Directorio::~Directorio() {

	this->imprimirDirectorio();

	if (bucketActual != NULL) {
		delete bucketActual;
	}

	for (offset i=0; i<this->nroCeldas; i++) {
		Bytes dato;
		dato.agregarAlFinal(&direcciones[i],sizeof(offset));
		archivoDirectorio->guardar(dato,i);
	}

	delete archivoDirectorio;
	delete archivoBuckets;

	if (direcciones != NULL)
		delete [] direcciones;

	cout << "fin";
}

void Directorio::crearOLevantar(const string &path) {

	archivoDirectorio = FabricaRecursosAlmacenamiento::getFabrica()->crearRecursoAccesoDirecto(path + "_hashdir.dat", sizeof(offset));
	archivoBuckets = FabricaRecursosAlmacenamiento::getFabrica()->crearRecursoAccesoDirecto(path + "_hashbuckets.dat", tamanioBucket);

	// si esta vacio creo un directorio nuevo de dos direcciones
	if (archivoDirectorio->estaVacio()) {
		this->profundidad = 1;
		this->nroCeldas = 2;
		this->direcciones = new offset[2];

		direcciones[0] = 0;
		direcciones[1] = 0;
		nroCeldas = 2;
		Bucket bucket(tamanioBucket,this,0);
		bucket.nroComponente = 0;
		guardarBucket(&bucket);

	// si no esta vacio lo leo del archivo
	} else {

		IteradorRegistrosFijos* it = (IteradorRegistrosFijos*)archivoDirectorio->getIterador();
		offset aux = it->getCantDatos();
		this->nroCeldas = aux;
		direcciones = new offset[this->nroCeldas];
		this->profundidad = 0;

		while (aux != 1) {
			aux = aux / 2;
			this->profundidad++;
		}

		for (unsigned i=0; i<this->nroCeldas; i++) {
			Bytes dato = it->getSiguiente();
			offset nroBucket;
			dato.leer(&nroBucket,0,sizeof(offset));
			direcciones[i] = nroBucket;
		}

		delete it;
	}

}


bool Directorio::insertar(Elemento &elemento) {

	Key clave = elemento.getClave();

	this->cargarBucket(clave);
	return this->bucketActual->agregar(elemento);
}

bool Directorio::borrar(Key &clave, offset& numComponente) {


	if (seEncuentraAlmacenada(clave)) {
		this->cargarBucket(clave);
		Elemento elemento;
		obtenerElemento(clave,elemento);
		numComponente = elemento.getDato();
		this->bucketActual->remover(clave);
		return true;
	} else {
		return false;
	}
}

bool Directorio::obtenerElemento(Key &clave,Elemento &elemento) {

	/* Cargo el Bucket perteneciente a la clave */
	this->cargarBucket(clave);
	return this->bucketActual->obtenerElemento(clave,elemento);

	return false;
}

bool Directorio::reducirTamanio() {

	if (profundidad == 0) {
		return false;
	}

	for (offset i = 0; i < nroCeldas; i += 2) {
		if (direcciones[i] != direcciones[i+1]) {
			return false;
		}
	}

	this->nroCeldas = this->nroCeldas/2;
	offset* direccionesNuevas = new offset[nroCeldas];

	for (offset j = 0;j < nroCeldas; j++) {
		direccionesNuevas[j] = direcciones[j*2];
	}

	delete direcciones;
	direcciones = direccionesNuevas;
	profundidad--;

	return true;
}

void Directorio::setProfundidad(offset profundidad) {

	this->profundidad = profundidad;
	this->nroCeldas = pow(2,this->profundidad);
}

bool Directorio::seEncuentraAlmacenada(Key &clave) {

	this->cargarBucket(clave);

	Bytes aux = this->bucketActual->obtener(clave.serializarse(),this->extractor);

	bool almacenado = !aux.estaVacio();

	return almacenado;
}

void Directorio::cargarBucket(Key &clave) {

	if (bucketActual != NULL) {
		delete bucketActual;
		bucketActual = NULL;
	}

	offset dirBucket = this->obtenerDireccionBucket(clave, this->profundidad);
	offset nroComponente = direcciones[dirBucket];
	this->bucketActual = obtenerBucketdeArchivo(nroComponente);
}

offset Directorio::obtenerDireccionBucket(Key &clave, offset profundidad) {

	offset offsetbucket = this->hash.crearDireccion(clave,profundidad);

	return offsetbucket;
}

bool Directorio::duplicarTamanio() {

	offset* nuevasDirecciones = new offset[this->nroCeldas*2];

	for (cantBytes i = 0 ; i < this->nroCeldas; i++) {

		nuevasDirecciones[i*2] = this->direcciones[i];
		nuevasDirecciones[i*2 + 1] = this->direcciones[i];
	}

	delete[] this->direcciones;

	this->direcciones = nuevasDirecciones;

	this->nroCeldas *= 2;
	this->profundidad++;

	return true;
}

bool Directorio::guardarBucket(Bucket *bucket) {

	if (bucket == NULL) {
		return false;
	}

	this->archivoBuckets->guardar(bucket->serializarse(),bucket->nroComponente);

	return true;
}


bool Directorio::insertarBucket(Bucket *bucket,offset inicial, offset final) {

	if (bucket == NULL) {
		return false;
	}

	offset nroComponente = this->archivoBuckets->guardar(bucket->serializarse());
	bucket->nroComponente = nroComponente;

	for (offset i = inicial; i <= final; i++) {
		direcciones[i] = nroComponente;
	}

	return true;
}

void Directorio::imprimirDirectorio() {

	cout << endl;
	cout << "Directorio: " << endl;

	offset anterior = -1;

	for (cantBytes i = 0; i < this->nroCeldas; i++) {

		cout << "[" << i << "] = > " << this->direcciones[i] << ": ";

		if (direcciones[i] != anterior) {
			anterior = direcciones[i];
			Bucket* bucket = obtenerBucketdeArchivo(direcciones[i]);
			bucket->imprimirClaves();
			delete bucket;
		} else {
			cout << endl;
		}
	}
	cout << endl;
}

Bucket* Directorio::obtenerBucketdeArchivo(offset nroComponente) {

	Bytes bytesBucket = this->archivoBuckets->cargar(nroComponente);

	if (bytesBucket.getTamanio() == 0) {
		cerr << "Error: error al intentar cargar el bucket. "<< endl;
		return NULL;
	}

	Bucket* bucket = new Bucket(this->tamanioBucket,this);
	bucket->nroComponente = nroComponente;
	bucket->hidratarse(bytesBucket);

	return bucket;
}
